<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2007 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------

defined('THINK_PATH') or exit();
/**
 * Mysqli数据库驱动类
 * @category   Think
 * @package  Think
 * @subpackage  Driver.Db
 * @author    liu21st <liu21st@gmail.com>
 */
class DbMysqli extends Db{

	/**
	 * 架构函数 读取数据库配置信息
	 * @access public
	 * @param array $config 数据库配置数组
	 */
	private $is_spare=false;//[sae]是否连接的为备用数据库
	public function __construct($config=''){
		if ( !extension_loaded('mysqli') ) {
			throw_exception(L('_NOT_SUPPERT_').':mysqli');
		}
		if(!empty($config)) {
			$this->config   =   $config;
			if(empty($this->config['params'])) {
				$this->config['params'] =   '';
			}
		}
	}

	/**
	 * 连接数据库方法
	 * @access public
	 * @throws ThinkExecption
	 */
	public function connect($config='',$linkNum=0) {
		if ( !isset($this->linkID[$linkNum]) ) {
			if(empty($config))  $config =   $this->config;
			$this->linkID[$linkNum] = new mysqli($config['hostname'],$config['username'],$config['password'],$config['database'],$config['hostport']?intval($config['hostport']):3306);
			if (mysqli_connect_errno() || C('SPARE_DB_DEBUG')){
				$errStr=mysqli_connect_error();
				$errno=mysqli_connect_errno();
				if($errno==13047 || C('SPARE_DB_DEBUG')){
					if(C('SMS_ALERT_ON')) Sms::send('mysql超额被禁用,请在SAE日志中心查看详情', $errStr,Sms::MYSQL_ERROR);
					//[sae]启动备用数据库
					if(C('SPARE_DB_HOST')){
						$this->linkID[$linkNum]=new mysqli(C('SPARE_DB_HOST'),C('SPARE_DB_USER'),C('SPARE_DB_PWD'),C('SPARE_DB_NAME'),C('SPARE_DB_PORT')?intval(C('SPARE_DB_PORT')):3306);
						if(mysqli_connect_error()){
							throw_exception(mysqli_connect_errno());
						}
						$this->is_spare=true;
					}else{
						throw_exception($errStr);
					}
					//标记使用备用数据库状态
				}else{
					//[sae] 短信预警
					if(C('SMS_ALERT_ON')) Sms::send('数据库连接时出错,请在SAE日志中心查看详情', $errStr,Sms::MYSQL_ERROR);
					throw_exception($errStr);
				}
			}
			$dbVersion = $this->linkID[$linkNum]->server_version;
			if ($dbVersion >= '4.1') {
				// 设置数据库编码 需要mysql 4.1.0以上支持
				$this->linkID[$linkNum]->query("SET NAMES '".C('DB_CHARSET')."'");
			}
			//设置 sql_model
			if($dbVersion >'5.0.1'){
				$this->linkID[$linkNum]->query("SET sql_mode=''");
			}
			// 标记连接成功
			$this->connected    =   true;
			//注销数据库安全信息
			if(1 != C('DB_DEPLOY_TYPE')) unset($this->config);
		}
		return $this->linkID[$linkNum];
	}

	/**
	 * 释放查询结果
	 * @access public
	 */
	public function free() {
		$this->queryID->free_result();
		$this->queryID = null;
	}

	/**
	 * 执行查询 返回数据集
	 * @access public
	 * @param string $str  sql指令
	 * @return mixed
	 */
	public function query($str) {
		$this->initConnect(false);
		if ( !$this->_linkID ) return false;
		$this->queryStr = $str;
		//释放前次的查询结果
		if ( $this->queryID ) $this->free();
		N('db_query',1);
		// 记录开始执行时间
		G('queryStartTime');
		$this->queryID = $this->_linkID->query($str);
		// 对存储过程改进
		if( $this->_linkID->more_results() ){
			while (($res = $this->_linkID->next_result()) != NULL) {
				$res->free_result();
			}
		}
		$this->debug();
		if ( false === $this->queryID ) {
			$this->error();
			return false;
		} else {
			$this->numRows  = $this->queryID->num_rows;
			$this->numCols    = $this->queryID->field_count;
			return $this->getAll();
		}
	}

	/**
	 * 执行语句
	 * @access public
	 * @param string $str  sql指令
	 * @return integer
	 */
	public function execute($str) {
		//[sae] 判断是否开启了备用数据库
		if($this->is_spare && !C('SPARE_DB_WRITEABLE')){
			$fun=C('SPARE_INFO_FUNCTION');
			if($fun){
				$fun();
				exit();
			}
			$this->error='mysql out of quota and spare db not writeable';
			return false;
		}
		$this->initConnect(true);
		if ( !$this->_linkID ) return false;
		$this->queryStr = $str;
		//释放前次的查询结果
		if ( $this->queryID ) $this->free();
		N('db_write',1);
		// 记录开始执行时间
		G('queryStartTime');
		$result =   $this->_linkID->query($str);
		$this->debug();
		if ( false === $result ) {
			$this->error();
			return false;
		} else {
			$this->numRows = $this->_linkID->affected_rows;
			$this->lastInsID = $this->_linkID->insert_id;
			return $this->numRows;
		}
	}

	/**
	 * 启动事务
	 * @access public
	 * @return void
	 */
	public function startTrans() {
		$this->initConnect(true);
		//数据rollback 支持
		if ($this->transTimes == 0) {
			$this->_linkID->autocommit(false);
		}
		$this->transTimes++;
		return ;
	}

	/**
	 * 用于非自动提交状态下面的查询提交
	 * @access public
	 * @return boolen
	 */
	public function commit() {
		if ($this->transTimes > 0) {
			$result = $this->_linkID->commit();
			$this->_linkID->autocommit( true);
			$this->transTimes = 0;
			if(!$result){
				$this->error();
				return false;
			}
		}
		return true;
	}

	/**
	 * 事务回滚
	 * @access public
	 * @return boolen
	 */
	public function rollback() {
		if ($this->transTimes > 0) {
			$result = $this->_linkID->rollback();
			$this->transTimes = 0;
			if(!$result){
				$this->error();
				return false;
			}
		}
		return true;
	}

	/**
	 * 获得所有的查询数据
	 * @access private
	 * @param string $sql  sql语句
	 * @return array
	 */
	private function getAll() {
		//返回数据集
		$result = array();
		if($this->numRows>0) {
			//返回数据集
			for($i=0;$i<$this->numRows ;$i++ ){
				$result[$i] = $this->queryID->fetch_assoc();
			}
			$this->queryID->data_seek(0);
		}
		return $result;
	}

	/**
	 * 取得数据表的字段信息
	 * @access public
	 * @return array
	 */
	public function getFields($tableName) {
		$result =   $this->query('SHOW COLUMNS FROM '.$this->parseKey($tableName));
		$info   =   array();
		if($result) {
			foreach ($result as $key => $val) {
				$info[$val['Field']] = array(
                    'name'    => $val['Field'],
                    'type'    => $val['Type'],
                    'notnull' => (bool) ($val['Null'] === ''), // not null is empty, null is yes
                    'default' => $val['Default'],
                    'primary' => (strtolower($val['Key']) == 'pri'),
                    'autoinc' => (strtolower($val['Extra']) == 'auto_increment'),
				);
			}
		}
		return $info;
	}

	/**
	 * 取得数据表的字段信息
	 * @access public
	 * @return array
	 */
	public function getTables($dbName='') {
		$sql    = !empty($dbName)?'SHOW TABLES FROM '.$dbName:'SHOW TABLES ';
		$result =   $this->query($sql);
		$info   =   array();
		if($result) {
			foreach ($result as $key => $val) {
				$info[$key] = current($val);
			}
		}
		return $info;
	}

	/**
	 * 替换记录
	 * @access public
	 * @param mixed $data 数据
	 * @param array $options 参数表达式
	 * @return false | integer
	 */
	public function replace($data,$options=array()) {
		foreach ($data as $key=>$val){
			$value   =  $this->parseValue($val);
			if(is_scalar($value)) { // 过滤非标量数据
				$values[]   =  $value;
				$fields[]   =  $this->parseKey($key);
			}
		}
		$sql   =  'REPLACE INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')';
		return $this->execute($sql);
	}

	/**
	 * 插入记录
	 * @access public
	 * @param mixed $datas 数据
	 * @param array $options 参数表达式
	 * @param boolean $replace 是否replace
	 * @return false | integer
	 */
	public function insertAll($datas,$options=array(),$replace=false) {
		if(!is_array($datas[0])) return false;
		$fields = array_keys($datas[0]);
		array_walk($fields, array($this, 'parseKey'));
		$values  =  array();
		foreach ($datas as $data){
			$value   =  array();
			foreach ($data as $key=>$val){
				$val   =  $this->parseValue($val);
				if(is_scalar($val)) { // 过滤非标量数据
					$value[]   =  $val;
				}
			}
			$values[]    = '('.implode(',', $value).')';
		}
		$sql   =  ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES '.implode(',',$values);
		return $this->execute($sql);
	}

	/**
	 * 关闭数据库
	 * @access public
	 * @return volid
	 */
	public function close() {
		if ($this->_linkID){
			$this->_linkID->close();
		}
		$this->_linkID = null;
	}

	/**
	 * 数据库错误信息
	 * 并显示当前的SQL语句
	 * @static
	 * @access public
	 * @return string
	 */
	public function error() {
		$this->error = $this->_linkID->error;
		if('' != $this->queryStr){
			$this->error .= "\n [ SQL语句 ] : ".$this->queryStr;
		}
		trace($this->error,'','ERR');
		//[sae] 短信预警
		if(C('SMS_ALERT_ON')) Sms::send('sql语句执行时出错，请在SAE日志中心查看详情',$this->error,Sms::MYSQL_ERROR);
		return $this->error;
	}

	/**
	 * SQL指令安全过滤
	 * @static
	 * @access public
	 * @param string $str  SQL指令
	 * @return string
	 */
	public function escapeString($str) {
		if($this->_linkID) {
			return  $this->_linkID->real_escape_string($str);
		}else{
			return addslashes($str);
		}
	}

	/**
	 * 字段和表名处理添加`
	 * @access protected
	 * @param string $key
	 * @return string
	 */
	protected function parseKey(&$key) {
		$key   =  trim($key);
		if(!preg_match('/[,\'\"\*\(\)`.\s]/',$key)) {
			$key = '`'.$key.'`';
		}
		return $key;
	}
}